#pragma once

#include "base.h"
#include "../updater/ohlc.h"
#include "../utils/time.h"

namespace mdtx::online::sampler
{
    template <typename Time, typename T, typename Duration = duration_type_t<Time>>
    class downsample_tumbling : public sampler_base<Time, T, 7, 7>
    {
        using base = sampler_base<Time, T, 7, 7>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;

    protected:
        Time next{}, epoch;
        Duration window;

        updater::ohlc4_tumbling<T> ohlc{};
        updater::vwap<T> vwap{};

        bool init{};

    public:
        explicit downsample_tumbling(Duration window, Time epoch = Time{})
            : epoch(epoch),
              window(window)
        {
        }

        /**
         * @brief update
         *
         * @param in time, open, high, low, close, vol, turnover, vwap
         * @return tuple_out time, open, high, low, close, vol, turnover, vwap
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [time, o, h, l, c, v, t, unused] = in;

            if (!init)
            {
                next = align_window_start(time, window, epoch) + window;
                init = true;
            }

            tuple_out sample{};
            if (time > next)
            {
                sample = detail::cat_time_tups(next, ohlc.pop(), vwap.pop());
                while (next < time)
                {
                    next += window;
                }
            }

            ohlc.insert_impl(o, h, l, c);
            vwap.insert_impl(v, t);

            return sample;
        }

        tuple_out flush() override
        {
            tuple_out sample{};
            if (ohlc.init)
            {
                sample = detail::cat_time_tups(next, ohlc.pop(), vwap.pop());
            }
            return sample;
        }
    };

    template <typename Time, typename T>
    class downsample_volclk : public sampler_base<Time, T, 7, 7>
    {
        using base = sampler_base<Time, T, 7, 7>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;

    protected:
        T window;
        Time last{};

        updater::ohlc4_tumbling<T> ohlc{};
        updater::vwap<T> vwap{};

    public:
        explicit downsample_volclk(T volume)
            : window(volume)
        {
        }

        /**
         * @brief update
         *
         * @param in time, open, high, low, close, vol, turnover, vwap
         * @return tuple_out time, open, high, low, close, vol, turnover, vwap
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [time, o, h, l, c, v, t, unused] = in;
            last = time;

            ohlc.insert_impl(o, h, l, c);
            vwap.insert_impl(v, t);

            tuple_out sample{};
            if (vwap.vol >= window)
            {
                sample = detail::cat_time_tups(time, ohlc.pop(), vwap.pop());
            }

            return sample;
        }

        tuple_out flush() override
        {
            tuple_out sample{};
            if (ohlc.init)
            {
                sample = detail::cat_time_tups(last, ohlc.pop(), vwap.pop());
            }
            return sample;
        }
    };

    template <typename Time, typename T>
    class downsample_tnvrclk : public sampler_base<Time, T, 7, 7>
    {
        using base = sampler_base<Time, T, 7, 7>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;

    protected:
        T window;
        Time last{};

        updater::ohlc4_tumbling<T> ohlc{};
        updater::vwap<T> vwap{};

    public:
        explicit downsample_tnvrclk(T turnover)
            : window(turnover)
        {
        }

        /**
         * @brief update
         *
         * @param in time, open, high, low, close, vol, turnover, vwap
         * @return tuple_out time, open, high, low, close, vol, turnover, vwap
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [time, o, h, l, c, v, t, unused] = in;
            last = time;

            ohlc.insert_impl(o, h, l, c);
            vwap.insert_impl(v, t);

            tuple_out sample{};
            if (vwap.tnvr >= window)
            {
                sample = detail::cat_time_tups(time, ohlc.pop(), vwap.pop());
            }

            return sample;
        }

        tuple_out flush() override
        {
            tuple_out sample{};
            if (ohlc.init)
            {
                sample = detail::cat_time_tups(last, ohlc.pop(), vwap.pop());
            }
            return sample;
        }
    };
}
